数据库接入准备
用户微服务需要对接数据库来持久化用户数据。本节使用 Prisma 作为 ORM 库,PostgreSQL 作为数据库。
安装依赖
pnpm add prisma @prisma/client
bash
配置 Prisma Schema
从原有项目中复制 prisma/schema.prisma,只保留用户相关的 model(User、Role、Permission 等),删除其他业务模型。确保数据库提供者配置为 PostgreSQL:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
prisma
生成 Prisma 客户端
npx prisma generate
bash
建议在 package.json 中添加快捷命令:
{
"scripts": {
"prisma:generate": "prisma generate"
}
}
json
创建 Database Service
// src/database.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
}
typescript
在 main.ts 中启用 shutdown hooks,否则 onModuleDestroy 不会执行:
// src/main.ts
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.GRPC,
options: {
package: 'user',
protoPath: join(__dirname, '../proto/user.proto')
}
})
app.enableShutdownHooks()
await app.listen()
}
typescript
在模块中注册 PrismaService:
@Module({
providers: [AppController, PrismaService],
})
export class AppModule {}
typescript
实现 findOne 方法
@GrpcMethod('UserService', 'findOne')
async findOne(payload: { username: string }) {
const user = await this.prisma.user.findUnique({
where: { username: payload.username }
})
if (!user) {
return { code: 10001, message: '用户不存在' }
}
return {
id: user.id,
username: user.username,
password: user.password,
code: 0,
message: 'success'
}
}
typescript
实现 create 方法
创建用户时需要处理角色关联和事务:
@GrpcMethod('UserService', 'create')
async create(payload: any) {
const roleId = 4 // 默认角色 ID(正式环境应从配置中读取)
try {
const userObj: any = { ...payload }
if (!userObj.roleId) {
userObj.roleId = roleId
}
const result = await this.prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: userObj })
return user
})
return {
id: result.id,
username: result.username,
code: 0,
message: 'success'
}
} catch (err) {
console.error('创建用户失败:', err)
return { code: 10002, message: '创建失败,用户名可能已存在' }
}
}
typescript
使用事务($transaction)确保数据一致性。try/catch 捕获唯一索引冲突等数据库错误。
测试
使用 grpcurl 测试两个接口:
# 查询用户
grpcurl -plaintext -d '{"username": "admin"}' localhost:5000 UserService/findOne
# 创建用户
grpcurl -plaintext -d '{"username": "newuser", "password": "123456"}' localhost:5000 UserService/create
bash
常见错误:
- 唯一索引冲突:用户名已存在,返回错误码而非崩溃
- 数据库连接失败:检查
.env中的DATABASE_URL配置
后续优化方向
- 封装 Database Module:将 PrismaService 封装为全局模块(
@Global()),避免在每个模块中重复注册 - 配置模块:将角色 ID 等硬编码值迁移到 ConfigModule 中管理
- 错误码规范:建立统一的错误码体系,方便调用方处理
↑